package cn.skyquant.quant4j.jforex.sdk.strategy;

import cn.skyquant.quant4j.api.account.AccountDTO;
import cn.skyquant.quant4j.api.account.AccountService;
import cn.skyquant.quant4j.api.account.AccountServiceProxy;
import cn.skyquant.quant4j.api.account.AccountType;
import cn.skyquant.quant4j.api.dto.ResultDTO;
import cn.skyquant.quant4j.api.ea.EAManagerDTO;
import cn.skyquant.quant4j.api.ea.EAService;
import cn.skyquant.quant4j.api.ea.EAServiceProxy;
import cn.skyquant.quant4j.api.enums.BorkerType;
import cn.skyquant.quant4j.api.goods.GoodsDTO;
import cn.skyquant.quant4j.api.goods.GoodsService;
import cn.skyquant.quant4j.api.goods.GoodsServiceProxy;
import cn.skyquant.quant4j.api.strategy.StrategyParameter;
import cn.skyquant.quant4j.api.strategy.StrategyTestService;
import cn.skyquant.quant4j.api.strategy.StrategyTestServiceProxy;
import cn.skyquant.quant4j.sdk.enums.Status;
import cn.skyquant.quant4j.sdk.util.time.CalendarUtils;
import com.dukascopy.api.*;

import java.io.PrintStream;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Formatter;
import java.util.List;
import java.util.Set;

/**
 * 所有继承此抽象策略类的都必须被系统所管理
 */
public abstract class AbstractStrategy implements IStrategy {

    @Configurable("运行时间间隔")
    public Period period = Period.ONE_HOUR;
    @Configurable("EAID")
    public int eaId = 0;
    @Configurable("测试数据是否入库")
    public boolean testdata = false;
    @Configurable("测试名称")
    public String testname = "普通测试";

    /*JForex平台相关*/
    protected IEngine engine;
    protected IConsole console;
    protected IHistory history;
    protected IContext context;
    protected IIndicators indicators;
    protected IUserInterface userInterface;
    protected IDataService dataService;
    protected IAccount account;
    protected JFUtils jfUtils;
    private boolean isStart =false;
    public int r_avg_spread = 10;
    public int t_avg_spread = 20;
    public long mpm_limit = 1000;

    /*业务相关*/
    protected AccountService accountService;
    private EAService eaService;
    private StrategyTestService testService;
    protected GoodsService goodsService;
    private EAManagerDTO eaManagerDTO;  //最重要的是ea.id
    protected AccountDTO accountDTO;    //最重要的是account.id
    protected MethodProcessManager mpm;
    private Integer testId;


    private long last_record_time = 0;
    public boolean log_start_stop = true;
    public boolean log_error = true;


    protected final void out(String format,Object...args){
        println(console.getOut(),format,args);
    }

    protected final void err(String format,Object...args){
        println(console.getErr(),format,args);
    }

    protected final void info(String format,Object...args){
        println(console.getInfo(),format,args);
    }

    protected final void notif(String format,Object...args){
        println(console.getNotif(),format,args);
    }

    protected final void warn(String format,Object...args){
        println(console.getWarn(),format,args);
    }

    protected final void println(PrintStream ps, String format, Object...args){
        Formatter formatter = new Formatter();
        formatter.format(format,args);
        String formatStr = formatter.toString();
        ps.println(formatStr);
    }

    //是否测试模式
    protected final boolean isTestMode(){
        return engine.getRunMode()==IEngine.RunMode.LOCAL && engine.getType()==IEngine.Type.TEST;
    }

    //是否模拟模式
    protected final boolean isDemoMode(){
        return engine.getRunMode()==IEngine.RunMode.LOCAL && engine.getType()==IEngine.Type.DEMO;
    }

    //是否实盘模式
    protected final boolean isRealMode(){
        return engine.getRunMode()==IEngine.RunMode.LOCAL && engine.getType()==IEngine.Type.LIVE;
    }

    /**
     * start的过程
     * 1.首先判断是否test模式,
     * @param context
     * @throws JFException
     */
    @Override
    public final void onStart(IContext context) throws JFException {
        this.engine = context.getEngine();
        this.console = context.getConsole();
        this.history = context.getHistory();
        this.context = context;
        this.indicators = context.getIndicators();
        this.userInterface = context.getUserInterface();
        this.dataService = context.getDataService();
        this.jfUtils = context.getUtils();
        this.account = context.getAccount();
        IWLabelData whiteLabelData = dataService.getWhiteLabelData();
        String accountCode = account.getAccountId();
        String accountName = account.getUserName();
        String borkerName = whiteLabelData.getLongName();
        String currency = account.getAccountCurrency().getSymbol();
        Set<IClientInfo> clientInfoSet = account.getClients();
        String type = null;
        switch (engine.getType()){
            case DEMO:type = "DEMO";break;
            case LIVE:type = "LIVE";break;
        }
        if(clientInfoSet.size()>1){
            type = AccountType.PAMM.name();
        }
        accountService = new AccountServiceProxy(accountCode,BorkerType.DUKASCOPY);
        goodsService = new GoodsServiceProxy(accountCode,BorkerType.DUKASCOPY);

        //首先要看一下是否有account，如果没有，则注册一个
        ResultDTO<AccountDTO> accountDTOResultDTO = accountService.get(accountCode,BorkerType.DUKASCOPY);
        if(accountDTOResultDTO.okData()){
            AccountDTO accountDTO = accountDTOResultDTO.data;
            if(accountDTO.type == null && type!=null){
                //如果已经有了,但是type不对的话，要更新type
                accountService.updateType(accountDTO.id,type);
            }
        }else {
            //如果account没有，则创造一个type
            accountDTOResultDTO = accountService.create(accountCode,BorkerType.DUKASCOPY,type,accountName,borkerName,currency,null);
        }

        //注册子帐号
        if(clientInfoSet!=null && !clientInfoSet.isEmpty()){
            for(IClientInfo clientInfo:clientInfoSet){
                if(clientInfo.getClientId().equals(account.getAccountId())){
                    continue;
                }
                String client_code = clientInfo.getClientId();
                String client_currency = clientInfo.getAccountCurrency().getSymbol();
                String client_type = AccountType.CLIENT.name();
                ResultDTO<AccountDTO> clientResultDTO = accountService.create(client_code,BorkerType.DUKASCOPY,client_type,null,borkerName,client_currency,accountCode);
                if(clientResultDTO.data==null){
                    continue;
                }
                AccountDTO clientAccount = clientResultDTO.data;
                console.getOut().println(String.format("client:%s",clientAccount.toString()));
            }
        }

        if(accountDTOResultDTO.okData()){
            accountDTO = accountDTOResultDTO.data;
        }else {
            context.stop();
            return;
        }

        if(accountDTO==null){
            context.stop();
            return;
        }

        //第二步，检测EA的状态（只在真实环境中才需要检测）
        if(isRealMode()){
            eaService = new EAServiceProxy(accountCode,BorkerType.DUKASCOPY);
            ResultDTO<EAManagerDTO> eaDTO = eaService.getOrCreate(getEaName(),eaId,accountDTO.id);
            if(eaDTO.okData() && eaDTO.data.eaStatus== Status.A){
                eaManagerDTO = eaDTO.data;
                out("run.id = %s",eaManagerDTO.id);
                eaService.ready(eaManagerDTO.id,null,null,null);
                context.stop();
                return;
            }else {
                err("account[%s %s],ea[%s %d]:no ea_manager:%s,quit!",accountCode,account.getUserName(),getName(),eaId,eaDTO.message);
            }

            if(eaManagerDTO==null){
                context.stop();
                return;
            }
        }

        if(log_start_stop) out("[%s %s] starting...",getName(),getVersion());
        onStartEx();
        if(isRealMode()){
            eaService.start(eaManagerDTO.id);
        }
        isStart = true;
        if (isTestMode() && testdata) {
            testService = new StrategyTestServiceProxy(accountCode,BorkerType.DUKASCOPY);
            List<StrategyParameter> parameterList = new ArrayList();
            Class c = this.getClass();
            Field[] fields = c.getFields();
            for (Field field : fields) {
                Configurable config = field.getAnnotation(Configurable.class);
                if (config != null) {
                    String desc = config.value();
                    if (desc != null) {
                        try {
                            Object obj = field.get(this);
                            if (obj != null) {
                                String value = obj.toString();
                                String name = field.getName();
                                StrategyParameter strategyParameter = new StrategyParameter(name, desc, value);
                                parameterList.add(strategyParameter);
                            }
                        } catch (Exception e) {
                        }
                    }
                }
            }
            String parameter = StrategyParameter.toJSONString(parameterList);
            ResultDTO<Integer> resultDTO = testService.start(getName(), getVersion(), testname, null, context.getTime(), parameter, account.getEquity());
            last_testdata_time = context.getTime();
            daily_open = account.getEquity();
            if (resultDTO.okData()) {
                testId = resultDTO.data;
            } else {
                if (log_start_stop && log_error) {
                    err(resultDTO.message);
                }
            }
        }
        updateAccountInfo(account);//更新账户资产信息
        info("account:[%s %s],ea:[%s %s] start successfully.^_^",accountCode,account.getUserName(),getName(),getVersion());
    }

    @Override
    public final void onStop() throws JFException {
        if(isStart){
            onStopEx();
            if(isRealMode() && eaManagerDTO!=null){
                eaService.stop(eaManagerDTO.id);
            }
        }
        if (testdata && isTestMode() && testId != null) {
            testService.end(testId, context.getTime(), account.getEquity());
            testId = null;
        }
        out("[%s %s] stoped",getName(),getVersion());
        isStart = false;
        eaService = null;
        testService =null;
        eaManagerDTO = null;
        accountDTO = null;
        mpm = null;
        last_record_time = 0;
    }

    //更新账户余额和净资产
    protected void updateAccountInfo(IAccount account){
        long time = context.getTime();
        if(time > last_record_time+period.getInterval()){
            last_record_time = time;
            String accountCode = account.getAccountId();
            double balance = new BigDecimal(account.getBalance()).setScale(2, RoundingMode.HALF_UP).doubleValue();
            double equity = new BigDecimal(account.getEquity()).setScale(2, RoundingMode.HALF_UP).doubleValue();
            double usedMargin = new BigDecimal(account.getUsedMargin()).setScale(2, RoundingMode.HALF_UP).doubleValue();
            double used_r = usedMargin/equity;
            accountService.update(accountCode,BorkerType.DUKASCOPY,balance,equity,usedMargin,context.getTime());

            Set<IClientInfo> clientInfos = account.getClients();
            for(IClientInfo clientInfo:clientInfos){
                if(clientInfo.getClientId().equals(account.getAccountId())){
                    continue;
                }
                double c_balance = new BigDecimal(clientInfo.getBalance()).setScale(2, RoundingMode.HALF_UP).doubleValue();
                double c_equity = new BigDecimal(clientInfo.getEquity()).setScale(2, RoundingMode.HALF_UP).doubleValue();
                double c_used_margin = new BigDecimal(c_equity * used_r).setScale(2, RoundingMode.HALF_UP).doubleValue();
                accountService.update(clientInfo.getClientId(),BorkerType.DUKASCOPY,c_balance,c_equity,c_used_margin,context.getTime());
            }
        }
    }

    @Override
    public final void onTick(Instrument instrument, ITick tick) throws JFException {
        if(!isStart) return;
        onTickEx(instrument,tick);
    }

    private long last_heartbeat_time = 0;//记录心跳的时间
    private long last_testdata_time;//记录测试数据的时间
    private Double daily_open;
    private Double daily_close;
    private Double daily_high;
    private Double daily_low;
    @Override
    public final void onBar(Instrument instrument, Period period, IBar askBar, IBar bidBar) throws JFException {
        if(!isStart) return;
        //1分钟记录1次心跳
        if(askBar.getTime()-last_heartbeat_time>Period.ONE_MIN.getInterval() && isRealMode()){
            String date = CalendarUtils.formatTightStard(context.getTime());
            last_heartbeat_time = askBar.getTime();
            eaService.heartbeat(eaManagerDTO.id,date,null);
        }
        if (testdata && isTestMode() && testId != null && askBar.getTime() - last_testdata_time > Period.DAILY.getInterval()) {
            last_testdata_time = askBar.getTime();
            daily_close = account.getEquity();
            if (daily_high != null && daily_low != null) {
                testService.updateDaily(testId, last_testdata_time, account.getBalance(), account.getEquity(), daily_open, daily_close, daily_high, daily_low);
            }
            daily_open = account.getEquity();
            daily_high = null;
            daily_low = null;
        }
        onBarEx(instrument,period,askBar,bidBar);
    }


    @Override
    public final void onMessage(IMessage message) throws JFException {
        if(!isStart) return;
        if (testdata && isTestMode() && testId != null) {
            IOrder order = message.getOrder();
            if (order == null) {
                return;
            }
            switch (message.getType()) {
                case ORDER_FILL_OK:
                    testService.addOpen(testId, message.getCreationTime(), order.getInstrument().name(), order.getOpenPrice(), order.getAmount(), order.getOrderCommand().name(), order.getStopLossPrice(), order.getTakeProfitPrice());
                    break;
                case ORDER_CLOSE_OK:
                    if (order.getState()== IOrder.State.CLOSED &&
                        order.getProfitLossInUSD()!=0) {
                        testService.addClose(testId, message.getCreationTime(), order.getInstrument().name(), order.getOpenPrice(), order.getClosePrice(), order.getAmount(), order.getOrderCommand().name(), order.getProfitLossInUSD(), order.getCommissionInUSD());
                    }
                    break;
            }
        }
        onMessageEx(message);
    }

    protected final void addStatistics(long beginTime, long endTime, double beginMoney, double endMoney, int tradeLevel,double profit, double amount,int distance){
        if (testdata && isTestMode() && testId != null) {
            testService.addStatistics(testId,beginTime,endTime,beginMoney,account.getEquity(),tradeLevel,profit,amount,distance);
        }
    }


    @Override
    public final void onAccount(IAccount account) throws JFException {
        if(!isStart) return;
        if (testdata && isTestMode() && testId != null) {
            double equity = account.getEquity();
            if (daily_high == null || equity > daily_high) {
                daily_high = equity;
            }
            if (daily_low == null || equity < daily_low) {
                daily_low = equity;
            }
        }
        onAccountEx(account) ;
    }

    //批量安全提交订单,带点差判断
    protected void safeBatchSubmitOrder(List<SubmitOrder> orderList) {
        boolean safe = true;
        try {
            for (SubmitOrder order : orderList) {
                if (isSafe(order.instrument, order.orderCommand, order.price)) {
                    safe = false;
                    break;
                }
            }
        } catch (JFException e) {
            safe = false;
        }
        if (safe) {
            try {
                for (SubmitOrder order : orderList) {
                    engine.submitOrder(order.label, order.instrument, order.orderCommand, order.amount, order.price, order.slippage, order.stopLossPrice, order.takeProfitPrice, order.goodTillTime, order.comment);
                }
            } catch (JFException e) {

            }
        }
    }

    //安全提交订单,带点差判断
    protected IOrder safeSubmitOrder(String label, Instrument instrument, IEngine.OrderCommand orderCommand, double amount, double price, double slippage, double stopLossPrice, double takeProfitPrice, long goodTillTime, String comment) {
        IOrder order = null;
        try {
            if (isSafe(instrument, orderCommand, price)) {
                order = engine.submitOrder(label, instrument, orderCommand, amount, price, slippage, stopLossPrice, takeProfitPrice, goodTillTime, comment);
            }else{
                out("spread is to large");
            }
        } catch (JFException e) {
            err("submit order error:", e.getMessage());
        }
        return order;
    }

    private boolean isSafe(Instrument instrument, IEngine.OrderCommand orderCommand, double price) throws JFException {
        ITick tick = history.getLastTick(instrument);
        switch (orderCommand) {
            case BUY:
            case SELL:
                ResultDTO<GoodsDTO> goodsDTOResultDTO = goodsService.get(instrument.name(), BorkerType.DUKASCOPY);
                if (goodsDTOResultDTO.okData()) {
                    GoodsDTO goodsDTO = goodsDTOResultDTO.data;
                    double avgSpread = goodsDTO.avgSpread;
                    double diff = tick.getAsk() - tick.getBid();
                    double x = isTestMode()?t_avg_spread:r_avg_spread;
                    if (diff < avgSpread * x) {
                        return true;
                    }
                }
                break;
            case BUYLIMIT:
                if (price < tick.getBid()) {
                    return true;
                }
                break;
            case SELLLIMIT:
                if (price > tick.getAsk()) {
                    return true;
                }
                break;
            case BUYSTOP:
                if (price > tick.getAsk()) {
                    return true;
                }
                break;
            case SELLSTOP:
                if (price < tick.getBid()) {
                    return true;
                }
                break;
        }
        return false;
    }

    /*需要子类实现的方法*/
    protected abstract String getVersion();
    protected abstract String getName();
    protected abstract String getEaName();
    protected abstract void onStartEx();
    protected abstract void onTickEx(Instrument instrument, ITick tick);
    protected abstract void onBarEx(Instrument instrument, Period period, IBar askBar, IBar bidBar);
    protected abstract void onMessageEx(IMessage message);
    protected abstract void onAccountEx(IAccount account);
    protected abstract void onStopEx();
}
